/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.swing;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.IllegalComponentStateException;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JViewport;
import javax.swing.SwingConstants;
import javax.swing.event.EventListenerList;
import javax.swing.event.MouseInputAdapter;

import org.isqlviewer.ui.dnd.TabbedPaneDropTarget;

/**
 * Enhanced tabbed pane with new input events and lockable and closable tabs.
 * <p>
 * 
 * @author Mark A. Kobold
 * @version 1.0
 */
public class EnhancedTabbedPane extends JTabbedPane {

    private static final long serialVersionUID = 9139921016326191363L;

    public final static int ACTION_TAB_CLICKED = 0xFD00;
    public final static int ACTION_TAB_DBL_CLICKED = 0xFD01;

    protected static final Icon LOCK_ICON = SwingUtilities.loadIconResource("encrypted", 16);
    protected static final Icon CLOSE_ICON = SwingUtilities.loadIconResource("close_view", 16);

    protected int maxtabcount = -1;
    protected int oldestIndex = -1;

    private static final String CLIENT_IS_LOCKED = "EnhancedTabbedPane.isLocked";
    private transient Vector<ActionListener> actionListeners;
    private transient EventListenerList eventListeners = new EventListenerList();
    private transient Hashtable<JComponent, Icon> iconCache;

    public EnhancedTabbedPane() {

        this(SwingConstants.TOP, 5);
    }

    public EnhancedTabbedPane(int tabplacement) {

        this(tabplacement, 5);
    }

    public EnhancedTabbedPane(int TabPlacement, int MaxTabCount) {

        super(TabPlacement);
        this.maxtabcount = MaxTabCount;
        iconCache = new Hashtable<JComponent, Icon>(maxtabcount);
        TabMouseInputHandler ma = new TabMouseInputHandler();
        addMouseListener(ma);
        addMouseMotionListener(ma);
        setDropTarget(new TabbedPaneDropTarget(this, this, null));
    }

    @Override
    public void addTab(String s, Component c) {

        addTab(s, null, c, null);
    }

    @Override
    public void addTab(String s, Icon icon, Component c) {

        addTab(s, new CompoundIcon(CLOSE_ICON, icon), c, null);
    }

    @Override
    public void addTab(String s, Icon icon, Component c, String tip) {

        if (getTabCount() < maxtabcount) {
            if (c instanceof JComponent) {
                JComponent jc = (JComponent) c;
                jc.putClientProperty(CLIENT_IS_LOCKED, Boolean.FALSE);
            }
            super.addTab(s, new CompoundIcon(null, icon), c, tip);
            int index = indexOfComponent(c);
            fireTabAdded(index, c);
        } else {
            int tab = locateFirstUnlockedTab();
            if (tab >= 0 && tab < getTabCount()) {
                setTitleAt(tab, s);
                setIconAt(tab, new CompoundIcon(null, icon));
                setComponentAt(tab, c);
                setToolTipTextAt(tab, tip);
            } else {
                throw new IllegalStateException();
            }
        }
    }

    @Override
    public void setIconAt(int idx, Icon ico) {

        try {
            CompoundIcon icon = (CompoundIcon) getIconAt(idx);
            icon.setRightIcon(ico);
        } catch (Throwable t) {
        } finally {
            invalidate();
            repaint();
        }
    }

    @Override
    public void removeTabAt(int index) {

        if (isTabLocked(index)) {
            String msg = "LockedTab_Close_Warning";
            String title = "Warning";
            int res = JOptionPane.showConfirmDialog(this, msg, title, JOptionPane.YES_NO_OPTION);
            if (res == JOptionPane.NO_OPTION) {
                return;
            }
        }
        Component component = getComponent(index);
        iconCache.remove(component);
        super.removeTabAt(index);
        fireTabRemoved(index, component);
    }

    public void removeAllTabs() {

        synchronized (this) {
            for (int i = getTabCount() - 1; i >= 0; i--)
                removeTabAt(i);
        }
    }

    public boolean isEmpty() {

        return (getTabCount() == 0);
    }

    public void removeUnlockedTabs() {

        synchronized (this) {
            for (int i = getTabCount() - 1; i >= 0; i--)
                if (!isTabLocked(i))
                    removeTabAt(i);
        }
    }

    public String getSelectedToolTip() {

        int tab = getSelectedIndex();
        if (tab >= 0) {
            return getToolTipTextAt(tab);
        }
        return null;
    }

    public void setMaxTabCount(int newMax) {

        if (newMax < 1) {
            throw new IllegalArgumentException(Integer.toString(newMax));
        }
        if (newMax >= maxtabcount) {
            maxtabcount = newMax;
        } else {
            while (maxtabcount > newMax && getTabCount() > newMax) {
                removeTabAt(0);
            }
            maxtabcount = newMax;
        }
    }

    public void removeSelectedTab() {

        int tab = getSelectedIndex();
        if (tab >= 0) {
            removeTabAt(tab);
        }
    }

    public void lockSelectedTab() {

        int tab = getSelectedIndex();
        if (tab >= 0)
            lockTab(tab);
    }

    public void unlockSelectedTab() {

        int tab = getSelectedIndex();
        if (tab >= 0)
            unlockTab(tab);
    }

    public int getIndexOfTitle(String title) {

        if (title != null) {
            for (int i = 0; i < getTabCount(); i++) {
                if (title.equals(getTitleAt(i))) {
                    return i;
                }
            }
        }
        return -1;
    }

    public void setClosableTab(int index, boolean closable) {

        if (index < 0 || index >= getTabCount()) {
            return;
        }
        try {
            CompoundIcon icon = (CompoundIcon) getIconAt(index);
            icon.setLeftIcon((closable ? CLOSE_ICON : null));
        } catch (Throwable t) {
        } finally {
            invalidate();
            repaint();
        }
    }

    public boolean isTabLocked(int i) {

        if (i >= 0) {
            Component c = getComponent(i);
            if (c instanceof JComponent) {
                JComponent jc = (JComponent) c;
                Boolean flag = (Boolean) jc.getClientProperty(CLIENT_IS_LOCKED);
                return flag == null ? false : flag.booleanValue();
            }
            return false;
        }

        throw new IllegalArgumentException(Integer.toString(i));
    }

    public void unlockTab(int i) {

        if (i >= 0)
            if (isTabLocked(i)) {
                Component c = getComponent(i);
                if (c instanceof JComponent) {
                    JComponent jc = (JComponent) c;
                    jc.putClientProperty(CLIENT_IS_LOCKED, Boolean.FALSE);
                    Icon icon = iconCache.get(c);
                    setIconAt(i, icon);
                    validateTree();
                    repaint();
                }
            }
    }

    public void lockTab(int i) {

        if (i >= 0)
            if (!isTabLocked(i)) {
                Component c = getComponent(i);
                if (c instanceof JComponent) {
                    JComponent jc = (JComponent) c;
                    jc.putClientProperty(CLIENT_IS_LOCKED, Boolean.TRUE);
                    CompoundIcon icon = (CompoundIcon) getIconAt(i);
                    if (icon != null) {
                        Icon cache = icon.getRightIcon();
                        if (cache != null) {
                            iconCache.put(jc, cache);
                        }
                        icon.setRightIcon(LOCK_ICON);
                    }
                    validateTree();
                    repaint();
                } else {
                    String message = "Resultcontainer_Component_Error";
                    throw new IllegalComponentStateException(message);
                }
            }
    }

    public void removeActionListener(ActionListener l) {

        if (actionListeners != null && actionListeners.contains(l)) {
            Vector v = actionListeners;
            v.removeElement(l);
        }
    }

    public void addActionListener(ActionListener listener) {

        synchronized (listenerList) {
            listenerList.add(ActionListener.class, listener);
        }
    }

    public void addTabbedPaneListener(TabbedPaneLister listener) {

        synchronized (listenerList) {
            listenerList.add(TabbedPaneLister.class, listener);
        }
    }

    protected int locateFirstUnlockedTab() {

        if (oldestIndex >= getTabCount() || oldestIndex < 0) {
            oldestIndex = 0;
        }
        for (int i = oldestIndex; i < getTabCount(); i++) {
            if (!isTabLocked(i)) {
                oldestIndex = (i + 1);
                return i;
            }
        }
        oldestIndex = -1;
        return -1;
    }

    protected void fireActionPerformed(ActionEvent e) {

        ActionListener[] listeners = listenerList.getListeners(ActionListener.class);
        for (int i = 0; i < listeners.length; i++) {
            ActionListener delegate = listeners[i];
            synchronized (delegate) {
                delegate.actionPerformed(e);
            }
        }
    }

    protected void fireTabRemoved(int index, Component component) {

        TabbedPaneLister[] listeners = listenerList.getListeners(TabbedPaneLister.class);
        Component actualComponent = component;
        if (component instanceof JScrollPane) {
            JViewport viewPort = ((JScrollPane) component).getViewport();
            actualComponent = viewPort.getView();
        }
        for (int i = 0; i < listeners.length; i++) {
            TabbedPaneLister delegate = listeners[i];
            synchronized (delegate) {
                delegate.tabRemoved(this, index, actualComponent);
            }
        }
    }

    protected void fireTabAdded(int index, Component component) {

        TabbedPaneLister[] listeners = eventListeners.getListeners(TabbedPaneLister.class);
        Component actualComponent = component;
        if (component instanceof JScrollPane) {
            JViewport viewPort = ((JScrollPane) component).getViewport();
            actualComponent = viewPort.getView();
        }
        for (int i = 0; i < listeners.length; i++) {
            TabbedPaneLister delegate = listeners[i];
            synchronized (delegate) {
                delegate.tabAdded(this, index, actualComponent);
            }
        }
    }

    private class TabMouseInputHandler extends MouseInputAdapter {

        @Override
        public void mousePressed(MouseEvent e) {

            if (getSelectedIndex() < 0) {
                return;
            }
            if (e.getClickCount() == 2 && getSelectedIndex() >= 0) {
                fireActionPerformed(new ActionEvent(
                        EnhancedTabbedPane.this, ACTION_TAB_DBL_CLICKED, getSelectedToolTip(), e.getModifiers()));
            } else if (e.getClickCount() == 1) {
                int idx = getSelectedIndex();
                Rectangle r = getUI().getTabBounds(EnhancedTabbedPane.this, idx);
                if (!r.contains(e.getPoint())) {
                    return;
                }
                CompoundIcon ico = (CompoundIcon) getIconAt(idx);
                if (ico != null) {
                    if (ico.isOverIcon(e.getX(), e.getY())) {
                        removeSelectedTab();
                        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                        return;
                    }

                    ActionEvent evt = new ActionEvent(EnhancedTabbedPane.this, ACTION_TAB_CLICKED, "", e.getModifiers());
                    fireActionPerformed(evt);
                } else {
                    ActionEvent evt = new ActionEvent(EnhancedTabbedPane.this, ACTION_TAB_CLICKED, "", e.getModifiers());
                    fireActionPerformed(evt);
                }
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {

            try {
                if (getTabCount() >= 1) {
                    int idx = getSelectedIndex();
                    Rectangle r = getUI().getTabBounds(EnhancedTabbedPane.this, idx);
                    if (!r.contains(e.getPoint())) {
                        if (getCursor().getType() != Cursor.DEFAULT_CURSOR)
                            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                        return;
                    }
                    CompoundIcon ico = (CompoundIcon) getIconAt(idx);
                    repaint(r);
                    if (ico != null) {
                        if (ico.isOverIcon(e.getX(), e.getY())) {
                            setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                            return;
                        }
                    }
                }
            } catch (Exception ignored) {
            }

            if (getCursor().getType() != Cursor.DEFAULT_CURSOR) {
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        }
    }
}